#include "LightHelper.fx"


//=================================================================================================
// Light constant buffer
//=================================================================================================
cbuffer cbPerFrame
{
	DirectionLight gDirLight;
	float3         gEyePosW;
	float          gMinDist;
	float          gMaxDist;
	float          gMinTess;
	float          gMaxTess;
	float          gTexelCellSpaceU;
	float          gTexelCellSpaceV;
	float          gWorldCellSpace;
	float2         gTexScale = 50.0f;	
	float4         gWorldFrustumPlanes[6];
};


//=================================================================================================
// Postion and material constant buffer
//=================================================================================================
cbuffer cbPerObject
{	
	float4x4 gViewProj;
	Material gMaterial;
};


//=================================================================================================
// Texture resource
//=================================================================================================
Texture2DArray gLayerMapArray;
Texture2D      gBlendMap;
Texture2D      gHeightMap;


//=================================================================================================
// Sampler state
//=================================================================================================
SamplerState samLinear
{
	Filter   = MIN_MAG_MIP_LINEAR;
	AddressU = WRAP;
	AddressV = WRAP;
};
SamplerState samHeightmap
{
	Filter   = MIN_MAG_LINEAR_MIP_POINT;
	AddressU = CLAMP;
	AddressV = CLAMP;
};


//=================================================================================================
// Input struct vertex shader
//=================================================================================================
struct VertexIn
{
	float3 PosL    : POSITION;
	float2 Tex     : TEXCOORD0;
	float2 BoundsY : TEXCOORD1;
};


//=================================================================================================
// Output struct after vertex shader
//=================================================================================================
struct VertexOut
{
	float3 PosW    : POSITION;
	float2 Tex     : TEXCOORD0;
	float2 BoundsY : TEXCOORD1;
};


//=================================================================================================
// Vertex shader
//=================================================================================================
VertexOut VS(VertexIn vin)
{
	VertexOut vout;
	vout.PosW    = vin.PosL;
	vout.PosW.y  = gHeightMap.SampleLevel(samHeightmap, vin.Tex, 0).r;
	vout.Tex     = vin.Tex;
	vout.BoundsY = vin.BoundsY;	
	return vout;
}


//=================================================================================================
// Calculate Tesselation factor
//=================================================================================================
float CalcTessFactor(float3 p)
{
	float d = distance(p, gEyePosW);
	float s = saturate((d - gMinDist) / (gMaxDist - gMinDist));
	return pow(2, lerp(gMaxTess, gMinTess, s));
}


//=================================================================================================
// Returns true if the box is completely behind (in negative half space) of plane
//=================================================================================================
bool AabbBehindPlaneTest(float3 center, float3 extents, float4 plane)
{
	float3 n = abs(plane.xyz);
	float  r = dot(extents, n);
	float  s = dot(float4(center, 1.0f), plane);
	return (s + r) < 0.0f;
}


//=================================================================================================
// Returns true if the box is completely outside the frustum
//=================================================================================================
bool AabbOutsideFrustumTest(float3 center, float3 extents, float4 frustumPlanes[6])
{
	for(int i = 0; i < 6; ++i)
	{
		if(AabbBehindPlaneTest(center, extents, frustumPlanes[i]))
		{
			return true;
		}
	}	
	return false;
}


//=================================================================================================
// Input struct constant hull shader
//=================================================================================================
struct PatchTess
{
	float EdgeTess[4]   : SV_TessFactor;
	float InsideTess[2] : SV_InsideTessFactor;
};


//=================================================================================================
// Constant hull shader
//=================================================================================================
PatchTess ConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID)
{
	PatchTess pt;
	
	// We store the patch BoundsY in the first control point.
	float minY = patch[0].BoundsY.x;
	float maxY = patch[0].BoundsY.y;
	
	// Build axis-aligned bounding box. patch[2] is lower-left corner and patch[1] is upper-right corner.
	float3 vMin = float3(patch[2].PosW.x, minY, patch[2].PosW.z);
	float3 vMax = float3(patch[1].PosW.x, maxY, patch[1].PosW.z);
	
	float3 boxCenter  = 0.5f * (vMin + vMax);
	float3 boxExtents = 0.5f * (vMax - vMin);
	if(AabbOutsideFrustumTest(boxCenter, boxExtents, gWorldFrustumPlanes))
	{
		pt.EdgeTess[0]   = 0.0f;
		pt.EdgeTess[1]   = 0.0f;
		pt.EdgeTess[2]   = 0.0f;
		pt.EdgeTess[3]   = 0.0f;		
		pt.InsideTess[0] = 0.0f;
		pt.InsideTess[1] = 0.0f;	
	}
	else 
	{
		float3 e0 = 0.50f * (patch[0].PosW + patch[2].PosW);
		float3 e1 = 0.50f * (patch[0].PosW + patch[1].PosW);
		float3 e2 = 0.50f * (patch[1].PosW + patch[3].PosW);
		float3 e3 = 0.50f * (patch[2].PosW + patch[3].PosW);
		float3 c0 = 0.25f * (patch[0].PosW + patch[1].PosW + patch[2].PosW + patch[3].PosW);
		
		pt.EdgeTess[0]   = CalcTessFactor(e0);
		pt.EdgeTess[1]   = CalcTessFactor(e1);
		pt.EdgeTess[2]   = CalcTessFactor(e2);
		pt.EdgeTess[3]   = CalcTessFactor(e3);
		pt.InsideTess[0] = CalcTessFactor(c0);
		pt.InsideTess[1] = CalcTessFactor(c0);	
	}
	return pt;
}


//=================================================================================================
// Output struct hull shader
//=================================================================================================
struct HullOut
{
	float3 PosW : POSITION;
	float2 Tex  : TEXCOORD0;
};


//=================================================================================================
// Hull shader
//=================================================================================================
[domain("quad")]
[partitioning("fractional_even")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(4)]
[patchconstantfunc("ConstantHS")]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 4> p, uint i : SV_OutputControlPointID, uint patchId : SV_PrimitiveID)
{
	HullOut hout;
	hout.PosW = p[i].PosW;
	hout.Tex  = p[i].Tex;	
	return hout;
}


//=================================================================================================
// Output of domain shader
//=================================================================================================
struct DomainOut
{
	float4 PosH     : SV_POSITION;
    float3 PosW     : POSITION;
	float2 Tex      : TEXCOORD0;
	float2 TiledTex : TEXCOORD1;
};


//=================================================================================================
// Domain shader
//=================================================================================================
[domain("quad")]
DomainOut DS(PatchTess patchTess, float2 uv : SV_DomainLocation, const OutputPatch<HullOut, 4> quad)
{
	DomainOut dout;
	dout.PosW     = lerp(lerp(quad[0].PosW, quad[1].PosW, uv.x), lerp(quad[2].PosW, quad[3].PosW, uv.x), uv.y);
	dout.Tex      = lerp(lerp(quad[0].Tex,  quad[1].Tex,  uv.x), lerp(quad[2].Tex,  quad[3].Tex,  uv.x), uv.y);
	dout.TiledTex = dout.Tex * gTexScale;
	dout.PosW.y   = gHeightMap.SampleLevel(samHeightmap, dout.Tex, 0).r;
	dout.PosH     = mul(float4(dout.PosW, 1.0f), gViewProj);
	return dout;
}


//=================================================================================================
// Pixel shader
//=================================================================================================
float4 PS(DomainOut pin) : SV_TARGET
{
	float2 leftTex   = pin.Tex + float2(-gTexelCellSpaceU, 0.0f);
	float2 rightTex  = pin.Tex + float2(+gTexelCellSpaceU, 0.0f);
	float2 bottomTex = pin.Tex + float2(0.0f, +gTexelCellSpaceV);
	float2 topTex    = pin.Tex + float2(0.0f, -gTexelCellSpaceV);
	
	float leftY   = gHeightMap.SampleLevel(samHeightmap, leftTex,   0).r;
	float rightY  = gHeightMap.SampleLevel(samHeightmap, rightTex,  0).r;
	float bottomY = gHeightMap.SampleLevel(samHeightmap, bottomTex, 0).r;
	float topY    = gHeightMap.SampleLevel(samHeightmap, topTex,    0).r;
	
	float3 tangent = normalize(float3(+2.0f * gWorldCellSpace, rightY - leftY, +0.0f));
	float3 bitan   = normalize(float3(+0.0f, bottomY - topY, -2.0f * gWorldCellSpace)); 
	float3 normalW = cross(tangent, bitan);

	// Caculate light direction.
	float3 toEyeW = normalize(gEyePosW - pin.PosW);
	
	// Sample layers in texture array.
	float4 c0 = gLayerMapArray.Sample(samLinear, float3(pin.TiledTex, 0.0f));
	float4 c1 = gLayerMapArray.Sample(samLinear, float3(pin.TiledTex, 1.0f));
	float4 c2 = gLayerMapArray.Sample(samLinear, float3(pin.TiledTex, 2.0f));
	float4 c3 = gLayerMapArray.Sample(samLinear, float3(pin.TiledTex, 3.0f));
	float4 c4 = gLayerMapArray.Sample(samLinear, float3(pin.TiledTex, 4.0f)); 
	
	// Sample the blend map.
	float4 t  = gBlendMap.Sample(samLinear, pin.Tex); 
    
    // Blend the layers on top of each other.
    float4 texColor = c0;
    texColor = lerp(texColor, c1, t.r);
    texColor = lerp(texColor, c2, t.g);
    texColor = lerp(texColor, c3, t.b);
    texColor = lerp(texColor, c4, t.a);
 
	// Start with a sum of zero. 
	float4 ambient  = float4(0.0f, 0.0f, 0.0f, 0.0f);
	float4 diffuse  = float4(0.0f, 0.0f, 0.0f, 0.0f);
	float4 specular = float4(0.0f, 0.0f, 0.0f, 0.0f);

	// Sum the light contribution from each light source.
	float4 A, D, S;
	
	// Direction light.
	ComputeDL(gMaterial, gDirLight, normalW, toEyeW, A, D, S);
	ambient  += A;  
	diffuse  += D;
	specular += S;

	// Final color.
	float4 litColor = 0.5 * texColor * (1 + ambient + diffuse) + specular;
    return litColor;
}

technique11 TerrainShaderTech
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_5_0, VS()));
        SetHullShader(CompileShader(hs_5_0, HS()));
        SetDomainShader(CompileShader(ds_5_0, DS()));
		SetGeometryShader(NULL);
        SetPixelShader(CompileShader(ps_5_0, PS()));
    }
}